package com.fa4j.system.account.service.impl;

import com.fa4j.common.base.exception.CommonRequestError;
import com.fa4j.common.base.util.ValidUtil;
import com.fa4j.system.account.model.*;
import com.fa4j.system.account.repository.AccountPasswordRepository;
import com.fa4j.system.account.repository.AccountRepository;
import com.fa4j.system.account.service.AccountPasswordService;
import com.fa4j.system.settings.service.SystemSettingsService;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.time.LocalDateTime;
import java.util.Optional;

/**
 * 账号密码 服务接口实现
 *
 * @since 2025-05-06 16:16:26
 */
@Slf4j
@Service
@Validated
@RequiredArgsConstructor
public class AccountPasswordServiceImpl implements AccountPasswordService {

    private final SystemSettingsService systemSettingsService;

    private final AccountPasswordRepository accountPasswordRepository;
    private final AccountRepository accountRepository;

    private final PasswordEncoder passwordEncoder;
    private final ApplicationEventPublisher publisher;

    /**
     * 重置密码
     *
     * @param cmd 参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reset(@Valid AccountPwdResetCmd cmd) {
        Account account = accountRepository.findById(cmd.getAccountId())
                .orElseThrow(() -> CommonRequestError.NOT_FOUND.build("账号"));
        String password = genPassword(cmd);
        SystemPasswordProps props = systemSettingsService.getSettingsWithDefault(SystemPasswordProps.class);
        LocalDateTime expireTime = LocalDateTime.now().plusMinutes(props.getResetExpireMinutes());
        Optional<AccountPassword> pwdOpt = accountPasswordRepository.findByAccountIdAndType(cmd.getAccountId(), cmd.getType());
        if (pwdOpt.isPresent()) {
            AccountPassword accountPassword = pwdOpt.get();
            accountPassword.setPassword(passwordEncoder.encode(password));
            accountPassword.setExpireTime(expireTime);
            accountPassword.setOperatorId(cmd.getOperatorId());
            accountPasswordRepository.modify(accountPassword);
        } else {
            AccountPassword accountPassword = new AccountPassword();
            accountPassword.setAccountId(account.getId());
            accountPassword.setType(cmd.getType());
            accountPassword.setPassword(passwordEncoder.encode(password));
            accountPassword.setExpireTime(expireTime);
            accountPassword.setOperatorId(cmd.getOperatorId());
            accountPasswordRepository.create(accountPassword);
        }
        //发送密码修改事件
        AccountPasswordRestEvent event = new AccountPasswordRestEvent();
        event.setAccountId(account.getId());
        event.setType(cmd.getType());
        event.setPassword(password);
        event.setExpireMinutes(props.getResetExpireMinutes());
        event.setOperatorId(cmd.getOperatorId());
        publisher.publishEvent(event);
    }

    /**
     * 修改密码
     *
     * @param cmd 参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void modify(AccountPwdModifyCmd cmd) {
        cmd.getType().checkStrength(cmd.getNewPwd());// 校验密码强度
        Account account = accountRepository.findById(cmd.getAccountId())
                .orElseThrow(() -> CommonRequestError.NOT_FOUND.build("账号"));
        SystemPasswordProps props = systemSettingsService.getSettingsWithDefault(SystemPasswordProps.class);
        LocalDateTime expireTime = LocalDateTime.now().plusMinutes(props.getModifyExpireMinutes());
        Optional<AccountPassword> pwdOpt = accountPasswordRepository.findByAccountIdAndType(cmd.getAccountId(), cmd.getType());
        if (pwdOpt.isPresent()) {
            AccountPassword accountPassword = pwdOpt.get();
            // 验证旧密码
            ValidUtil.validNotEmpty("原密码", cmd.getOldPwd());
            if (passwordEncoder.matches(cmd.getOldPwd(), accountPassword.getPassword())) {
                accountPassword.setPassword(passwordEncoder.encode(cmd.getNewPwd()));
                accountPassword.setExpireTime(expireTime);
                accountPassword.setOperatorId(cmd.getOperatorId());
                accountPasswordRepository.modify(accountPassword);
            } else {
                throw PasswordError.ERROR_OLD_PASSWORD.build();
            }
        } else {
            AccountPassword accountPassword = new AccountPassword();
            accountPassword.setAccountId(cmd.getAccountId());
            accountPassword.setType(cmd.getType());
            accountPassword.setPassword(passwordEncoder.encode(cmd.getNewPwd()));
            accountPassword.setExpireTime(expireTime);
            accountPassword.setOperatorId(cmd.getOperatorId());
            accountPasswordRepository.create(accountPassword);
        }
        //发送密码修改事件
        AccountPasswordModifyEvent event = new AccountPasswordModifyEvent();
        event.setAccountId(account.getId());
        event.setType(cmd.getType());
        publisher.publishEvent(event);

    }

    /**
     * 生成密码
     */
    private String genPassword(AccountPwdResetCmd cmd) {
        SystemPasswordProps props = systemSettingsService.getSettingsWithDefault(SystemPasswordProps.class);
        String password = null;
        switch (cmd.getResetType()) {
            case CUSTOM -> {
                ValidUtil.validNotEmpty("密码", cmd.getPassword());
                cmd.getType().checkStrength(cmd.getPassword());
                password = cmd.getPassword();
            }
            case RANDOM -> password = RandomStringUtils.secure().nextAlphanumeric(8);
            case DEFAULT -> password = props.getDefaultPassword();
        }
        return password;
    }

    /**
     * 校验密码
     *
     * @param accountId 账号 ID
     * @param type      类型
     * @param pwd       密码
     */
    @Override
    public boolean validPwd(@NotNull Long accountId,
                            @NotNull AccountPasswordType type,
                            @NotBlank String pwd) {
        AccountPassword accountPassword = accountPasswordRepository.findByAccountIdAndType(accountId, type).orElse(null);
        if (accountPassword == null) {
            return false;
        }
        if (accountPassword.getExpireTime().isBefore(LocalDateTime.now())) {
            log.warn("账号[{}]的{}已经过期", accountPassword.getAccountId(), type.getName());
            throw PasswordError.PASSWORD_EXPIRED.build();
        }
        return passwordEncoder.matches(pwd, accountPassword.getPassword());
    }
}




